/*
 * @(#)Xfplot.java        2003/01/01
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfplot.view;

import xfuzzy.*;
import xfuzzy.lang.*;
import xfuzzy.util.*;
import xfuzzy.xfplot.model.XfplotConfig;
import xfuzzy.xfplot.model.XfplotColorModel;
import xfuzzy.xfplot.view.listener.*;

import java.io.*;
import java.util.Vector;
import javax.swing.*;
import java.awt.*;


/**
 * Representaci�n gr�fica del sistema difuso
 * 
 * @author Francisco Jos� Moreno Velo
 *
 */
public class Xfplot extends JFrame {
	
	//----------------------------------------------------------------------------//
	//                            CONSTANTES PRIVADAS                             //
	//----------------------------------------------------------------------------//

	/**
	 * C�digo asociado a la clase serializable
	 */
	private static final long serialVersionUID = 95505666603052L;
		
	/**
	 * C�digo para indicar representaciones en 3D
	 */
	private static final int MODE_3D = 0;
	
	/**
	 * C�digo para indicar representaciones en 2D
	 */
	private static final int MODE_2D = 1;
	
	//----------------------------------------------------------------------------//
	//                            MIEMBROS PRIVADOS                              //
	//----------------------------------------------------------------------------//

	/**
	 * Referencia a la ventana principal del entorno
	 */
	private Xfuzzy xfuzzy;
	
	/**
	 * Sistema difuso a representar
	 */
	private Specification spec;
	
	/**
	 * Valores a representar en 3D
	 */
	private double function3D[][];
	
	/**
	 * Indicador de que los valores en 3D se han calculado
	 */
	private boolean computed3D;
	
	/**
	 * Valores a representar en 2D
	 */
	private double function2D[];
	
	/**
	 * Modo de representaci�n (2D o 3D)
	 */
	private int plotmode;
	
	/**
	 * Configuraci�n de la aplicaci�n
	 */
	private XfplotConfig config;
	
	/**
	 * Objeto que desarrolla la representaci�n 3D
	 */
	private Xfplot3DPanel graph3D;
	
	/**
	 * Objeto que desarrolla la representaci�n 2D
	 */
	private Xfplot2DPanel graph2D;
	
	/**
	 * Desplazador horizontal
	 */
	private JSlider hslider;
	
	/**
	 * Desplazador vertical
	 */
	private JSlider vslider;
	
	/**
	 * Panel de la representaci�n 3D
	 */
	private JPanel panel3D;
	
	/**
	 * Campo de texto para introducir el n�mero de puntos por eje
	 */
	private JTextField samplestext;
	
	/**
	 * Selector de la variable X
	 */
	private JComboBox xcombo;
	
	/**
	 * Selector de la variable Y
	 */
	private JComboBox ycombo;
	
	/**
	 * Selector de la variable Z
	 */
	private JComboBox zcombo;
	
	//----------------------------------------------------------------------------//
	//                                CONSTRUCTOR                                 //
	//----------------------------------------------------------------------------//
	
	/**
	 * Constructor
	 */
	public Xfplot(Xfuzzy xfuzzy, Specification spec) {
		super("Xfplot");
		this.xfuzzy = xfuzzy;
		this.spec = spec;
		this.computed3D = false;
		this.config = new XfplotConfig(); 
		this.config.setSpecification(spec);
		
		int numinputs = spec.getSystemModule().getInputs().length;
		if(numinputs>1) {
			this.plotmode = MODE_3D;
			try { config.setPlotMode(MODE_3D); } catch(Exception ex) {}
		} else {
			this.plotmode = MODE_2D;
			try { config.setPlotMode(MODE_2D); } catch(Exception ex) {}
		}
		
		build();
		setConfig();
		actualize();
	}
		
 	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//
	
	/**
	 * Obtiene una referencia a la configuraci�n de la herramienta
	 */
	public XfplotConfig getConfig() {
		return this.config;
	}
	
	/**
	 * Devuelve la variable a representar en el eje X
	 */
	public Variable getXVariable() {
		Variable inputvar[] = spec.getSystemModule().getInputs();
		return inputvar[config.getXIndex()];
	}
	/**
	 * Devuelve la variable a representar en el eje Y	
	 */
	public Variable getYVariable() {
		Variable inputvar[] = spec.getSystemModule().getInputs();
		return inputvar[config.getYIndex()];
	}
	
	/**
	 * Devuelve la variable a representar en el eje Z
	 */
	public Variable getZVariable() {
		Variable outputvar[] = spec.getSystemModule().getOutputs();
		return outputvar[config.getZIndex()];
	}
	
	/**
	 * Devuelve las variables de entrada
	 */
	public Variable[] getInputVariables() {
		return spec.getSystemModule().getInputs();
	}
	
	/**
	 * Devuelve el valor de las variables de entrada
	 */
	public double[] getInputValues() {
		return this.config.getInputValues();
	}
	
	/**
	 * Devuelve los valores para representar la funcion en 2D
	 */
	public double[] get2DFunction() {
		return this.function2D;
	}
	
	/**
	 * Devuelve los valores para representar la funcion en 3D
	 */
	public double[][] getFunction() {
		return this.function3D;
	}
	
	/**
	 * Verifica que los valores de la funcion se han calculado
	 */
	public boolean is3DComputed() {
		return computed3D;
	}
	
	/**
	 * Asigna el valor de las variables de entrada
	 */
	public boolean setInputValues(double value[]) {
		try { this.config.setInputValues(value); return true; }
		catch(Exception ex) { return false; }
	}
	
	/**
	 * Actualiza la represedntaci�n en funci�n de las barras de desplazamiento
	 */
	public void changeSlider() {
		int hangle = hslider.getValue();
		int vangle = vslider.getValue();
		try { config.setSlides(hangle,vangle); } catch(Exception ex) {}
		graph3D.setHRotation(hangle/50.0 - 1.0);
		graph3D.setVRotation(vangle/100.0);
		graph3D.repaint();
	}
	
	/**
	 * Finaliza la aplicaci�n
	 */
	public void close() {
		if(xfuzzy == null) System.exit(0);
		else setVisible(false);
	}
	
	/**
	 * Actualiza la representaci�n gr�fica
	 */
	public void actualize() {
		if(plotmode == MODE_3D) {
			try { config.setSamples( Integer.parseInt(samplestext.getText()) ); }
			catch (Exception ex) { samplestext.setText( ""+config.getSamples() ); }
			function3D = config.get3DFunction();
			computed3D = true;
			graph3D.repaint();
		} else if(plotmode == MODE_2D) {
			try { config.setSamples( Integer.parseInt(samplestext.getText()) ); }
			catch (Exception ex) { samplestext.setText( ""+config.getSamples() ); }
			function2D = config.get2DFunction();
			graph2D.repaint();
		}
	}
	
	/**
	 * Acci�n asociada a la lista desplegable del eje X	
	 */
	public void changeXAxis() {
		int index = xcombo.getSelectedIndex();
		int xindex = config.getXIndex();
		int yindex = config.getYIndex();
		if(index == xindex) return;
		config.setXIndex(index);
		if(yindex == index) {
			config.setYIndex(xindex);
			ycombo.setSelectedIndex(xindex);
		}
		actualize();
	}
	
	/**
	 * Acci�n asociada a la lista desplegable del eje Y
	 */
	public void changeYAxis() {
		int index = ycombo.getSelectedIndex();
		int xindex = config.getXIndex();
		int yindex = config.getYIndex();
		if(index == yindex) return;
		config.setYIndex(index);
		if(xindex == index) {
			config.setXIndex(yindex);
			xcombo.setSelectedIndex(yindex);
		}
		actualize();
	}
	
	/**
	 * Acci�n asociada a la lista desplegable del eje Z
	 */
	public void changeZAxis() {
		config.setZIndex(zcombo.getSelectedIndex());
		actualize();
	}
	
	/**
	 * Acci�n asociada a la selecci�n de la representaci�n 2D
	 */
	public void set2DPlotMode() {
		if(plotmode == MODE_2D) return;
		plotmode = MODE_2D;
		try { config.setPlotMode(MODE_2D); } catch(Exception ex) {}
		Container content = getContentPane();
		content.remove(panel3D);
		content.add(graph2D);
		pack();
		actualize();
	}
	
	/**
	 * Acci�n asociada a la selecci�n de la representaci�n 3D
	 */
	public void set3DPlotMode() {
		if(plotmode == MODE_3D) return;
		plotmode = MODE_3D;
		try { config.setPlotMode(MODE_3D); } catch(Exception ex) {}
		Container content = getContentPane();
		content.remove(graph2D);
		content.add(panel3D);
		pack();
		actualize();
	}
	
	/**
	 * Acci�n asociada al cambio de modelo de color
	 */
	public void changeColor(String action) {
		try {
			int code = Integer.parseInt(action.substring(2));
			config.setColorMode(code);
			actualize();
		} catch(Exception ex) {}
	}
	
	/**
	 * Acci�n asociada a la edici�n de valores de entrada
	 */
	public void changeInputValues() {
		XfplotInputDialog dialog = new XfplotInputDialog(this);
		dialog.setVisible(true);
		actualize();
	}
	
	/**
	 * Almacena la imagen en un archivo de datos
	 */
	public void saveData() {
		File wdir;
		if(xfuzzy != null) wdir = xfuzzy.getWorkingDirectory();
		else wdir = new File(System.getProperty("user.dir"));
		XFileChooser chooser = new XFileChooser(wdir,XFileChooser.SAVE);
		if(chooser.showDialog() != XFileChooser.SELECTED) return;
		File file = chooser.getSelectedFile();
		if(file.exists()) {
			String question[] = new String[2];
			question[0] = "File "+file.getName()+" already exists.";
			question[1] = "Do you want to overwrite this file?";
			if( !XDialog.showQuestion(this, question) ) return;
		}
		try {
			FileOutputStream fos = new FileOutputStream(file);
			PrintStream stream = new PrintStream(fos);
			
			Variable xvar = getXVariable();
			Variable yvar = getYVariable();
			stream.println( config.getHeading() );
			
			if(plotmode == MODE_3D) {
				int samples = function3D.length;
				for(int i=0; i<samples; i++) {
					for(int j=0; j<samples; j++) {
						stream.print( ""+xvar.point(i*1.0/(samples-1)) );
						stream.print( " "+yvar.point(j*1.0/(samples-1)) );
						stream.println( " "+function3D[i][j] );
					}
					stream.println("");
				}
			} else {
				int samples = function2D.length;
				for(int i=0; i<samples; i++) {
					stream.print( ""+xvar.point(i*1.0/(samples-1)) );
					stream.println( " "+function2D[i] );
				}
			}
			
			stream.close();
		} catch(Exception ex) {}
	}
	
	/**
	 * Intenta grabar la imagen como un archivo JPEG
	 */
	public void saveImage() {
		File wdir;
		if(xfuzzy != null) wdir = xfuzzy.getWorkingDirectory();
		else wdir = new File(System.getProperty("user.dir"));
		XFileChooser chooser = new XFileChooser(wdir,XFileChooser.SAVE);
		Vector<String> filter = new Vector<String>();
		filter.add(".jpg");
		filter.add(".png");
		chooser.addFileFilter(filter,"JPEG and PNG Image Files");
		if(chooser.showDialog() != XFileChooser.SELECTED) return;
		File file = chooser.getSelectedFile();
		if(file.exists()) {
			String question[] = new String[2];
			question[0] = "File "+file.getName()+" already exists.";
			question[1] = "Do you want to overwrite this file?";
			if( !XDialog.showQuestion(this, question) ) return;
		}
		if(plotmode == MODE_3D) { this.graph3D.saveImage(file); }
		else if(plotmode == MODE_2D) { this.graph2D.saveImage(file); }
	}
	
	/**
	 * Recupera la configuraci�n de un fichero externo
	 */
	public void loadConfig() {
		File wdir;
		if(xfuzzy != null) wdir = xfuzzy.getWorkingDirectory();
		else wdir = new File(System.getProperty("user.dir"));
		XFileChooser chooser = new XFileChooser(wdir,XFileChooser.LOAD);
		chooser.addFileFilter(".xml","Xfuzzy Configuration Files");
		if(chooser.showDialog() != XFileChooser.SELECTED) return;
		File file = chooser.getSelectedFile();
		
		XmlParser parser = new XmlParser();
		parser.parseXML(file);
		XfplotConfig xfplotc = parser.getXfplotConfig();
		if(xfplotc != null) {
			config = xfplotc;
			config.setSpecification(this.spec);
			setConfig();
			actualize();
		} else {
			if(xfuzzy != null) xfuzzy.log(parser.getErrorMessage());
			else System.out.println(parser.getErrorMessage());
			Toolkit.getDefaultToolkit().beep();
		}
	}
		
	/**
	 * Guarda la configuraci�n en un fichero externo
	 */
	public void saveConfig() {		
		File wdir = xfuzzy.getWorkingDirectory();
		XFileChooser chooser = new XFileChooser(wdir,XFileChooser.SAVE);
		chooser.addFileFilter(".xml","Xfuzzy Configuration Files");
		int returnVal = chooser.showDialog();
		if(returnVal != XFileChooser.SELECTED) return;
		File file = chooser.getSelectedFile();
		
		boolean append = false;
		if(file.exists()) {
			String question[] = new String[3];
			question[0] = "File "+file.getName()+" already exists.";
			question[1] = "Do you want to overwrite this file?";
			question[2] = "(Otherwise the code will be append at the end of the file)";
			int result = XDialog.showYNQuestion(this, question);
			if(result == XDialog.CANCEL) return;
			if(result == XDialog.NO) append = true;
		}
		config.save(file,append);
	}
	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//
	
	/**
	 * Crea la ventana
	 */
	private void build() {
		Container content = getContentPane();
		content.setLayout(new BoxLayout(content,BoxLayout.Y_AXIS));
		content.add(createPlotPanel());
		
		setJMenuBar(createMenubar());
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		setIconImage( XfuzzyIcons.xfuzzy.getImage() );
		addWindowListener(new XfplotWindowListener(this));
		pack();
		setLocation();
	}
	
	/**
	 * Crea la barra de men�
	 */
	private JMenuBar createMenubar() {
		XfplotActionListener actlistener = new XfplotActionListener(this);
		
		Variable inputvar[] = spec.getSystemModule().getInputs();
		Variable outputvar[] = spec.getSystemModule().getOutputs();
		
		xcombo = createVariableList(inputvar,"XAxis");
		ycombo = createVariableList(inputvar,"YAxis");
		zcombo = createVariableList(outputvar,"ZAxis");
		
		samplestext = new JTextField("");
		samplestext.setBackground(XConstants.textbackground);
		samplestext.setBorder(BorderFactory.createLoweredBevelBorder());
		samplestext.addActionListener(actlistener);
		samplestext.setActionCommand("Samples");
		
		Dimension prefsize = xcombo.getPreferredSize();
		Dimension samplesize = new Dimension(50,prefsize.height);
		samplestext.setPreferredSize(samplesize);
		samplestext.setMinimumSize(samplesize);
		samplestext.setMaximumSize(samplesize);
		
		String fileitem[] = { "Save data","Save image",null,"Actualize",null,"Close"};
		String filecomm[] = { "SaveData","SaveImage","Actualize","Close"};
		XMenu file = new XMenu("File",fileitem,filecomm,actlistener);
		
		String plotitem[] = { "2-dimensional Plot","3-dimensional Plot"};
		String plotcomm[] = { "2DPlot","3DPlot"};
		XMenu plotmenu = new XMenu("Plot Mode",plotitem,plotcomm,actlistener);
		if(inputvar.length <2) plotmenu.setEnabled(false,1);
		
		JMenu colormenu = createColorMenu();
		
		JMenuItem item1 = new JMenuItem("Input Values");
		item1.setFont(XConstants.font);
		item1.setActionCommand("InputValues");
		item1.addActionListener(actlistener);
		JMenuItem item2 = new JMenuItem("Load Configuration");
		item2.setFont(XConstants.font);
		item2.setActionCommand("LoadConfig");
		item2.addActionListener(actlistener);
		JMenuItem item3 = new JMenuItem("Save Configuration");
		item3.setFont(XConstants.font);
		item3.setActionCommand("SaveConfig");
		item3.addActionListener(actlistener);
		
		JMenu conf = new JMenu("Configuration");
		conf.add(plotmenu);
		conf.add(colormenu);
		conf.add(item1);
		conf.addSeparator();
		conf.add(item2);
		conf.add(item3);
		
		JLabel xlabel = new JLabel("X Axis");
		JLabel ylabel = new JLabel("Y Axis");
		JLabel zlabel = new JLabel("Z Axis");
		JLabel slabel = new JLabel("Samples");
		xlabel.setForeground(Color.black);
		ylabel.setForeground(Color.black);
		zlabel.setForeground(Color.black);
		slabel.setForeground(Color.black);
		
		JMenuBar bar = new JMenuBar();
		bar.add(file);
		bar.add(Box.createHorizontalStrut(20));
		bar.add(conf);
		bar.add(Box.createHorizontalStrut(40));
		bar.add(Box.createHorizontalGlue());
		bar.add(xlabel);
		bar.add(Box.createHorizontalStrut(10));
		bar.add(xcombo);
		bar.add(Box.createHorizontalStrut(20));
		bar.add(ylabel);
		bar.add(Box.createHorizontalStrut(10));
		bar.add(ycombo);
		bar.add(Box.createHorizontalStrut(20));
		bar.add(zlabel);
		bar.add(Box.createHorizontalStrut(10));
		bar.add(zcombo);
		bar.add(Box.createHorizontalStrut(20));
		bar.add(slabel);
		bar.add(Box.createHorizontalStrut(10));
		bar.add(samplestext);
		bar.add(Box.createHorizontalStrut(20));
		return bar;
	}
	
	/**
	 * Crea del men� de modelos de colores
	 */
	private JMenu createColorMenu() {
		XfplotActionListener actlistener = new XfplotActionListener(this);

		JMenu menu = new JMenu("Color Model");
		for(int i=0; i<XfplotColorModel.COUNTER; i++) {
			JMenuItem item = new JMenuItem(new XfplotColorIcon(i));
			item.setActionCommand("CM"+i);
			item.addActionListener(actlistener);
			menu.add(item);
		}
		return menu;
	}
	
	/**
	 * Crea una lista desplegable con las variables
	 */
	private JComboBox createVariableList(Variable[] var, String action) {
		XfplotActionListener actlistener = new XfplotActionListener(this);

		JComboBox combo = new JComboBox();
		combo.setBackground(XConstants.textbackground);
		for(int i=0; i<var.length; i++) combo.addItem(var[i]);
		combo.addActionListener(actlistener);
		combo.setActionCommand(action);
		
		Dimension prefsize = combo.getPreferredSize();
		Dimension size = new Dimension(100,prefsize.height);
		combo.setPreferredSize(size);
		combo.setMinimumSize(size);
		combo.setMaximumSize(size);
		return combo;
	}
	
	/**
	 * Crea el panel de la representaci�n gr�fica
	 */
	private JPanel createPlotPanel() {
		XfplotChangeListener listener = new XfplotChangeListener(this);
		
		this.graph2D = new Xfplot2DPanel(this);
		
		this.graph3D = new Xfplot3DPanel(this);
		this.hslider = new JSlider(JSlider.HORIZONTAL,0,100,50);
		this.hslider.addChangeListener(listener);
		this.vslider = new JSlider(JSlider.VERTICAL,0,100,30);
		this.vslider.addChangeListener(listener);
		this.vslider.setInverted(true);
		
		panel3D = new JPanel();
		panel3D.setLayout(new BorderLayout());
		panel3D.add(graph3D,BorderLayout.CENTER);
		panel3D.add(hslider,BorderLayout.SOUTH);
		panel3D.add(vslider,BorderLayout.EAST);
		
		if(plotmode == MODE_3D) return panel3D;
		return graph2D;
	}
	
	/**
	 * Coloca la ventana en la pantalla	
	 */
	private void setLocation() {
		Dimension frame = getSize();
		Dimension screen = getToolkit().getScreenSize();
		setLocation((screen.width - frame.width)/2,(screen.height - frame.height)/2);
	}
	
	/**
	 * Actualiza los campos con los valores de la configuraci�n
	 */
	private void setConfig() {
		xcombo.setSelectedIndex(config.getXIndex());
		ycombo.setSelectedIndex(config.getYIndex());
		zcombo.setSelectedIndex(config.getZIndex());
		samplestext.setText( ""+config.getSamples() );
		graph3D.setHRotation(config.getHSlide()/50.0 - 1.0);
		graph3D.setVRotation(config.getVSlide()/100.0);
	}
	
}
